package pjwstk.jps.interpreter.envs;

import java.util.*;

import pjwstk.jps.result.*;
import pjwstk.jps.datastore.*;

/**
 * @author s9111_s10304
 *
 */
public class ENVS implements IENVS
{
	private LinkedList<IENVSFrame> frameStack;
	
	public ENVS() {
		frameStack = new LinkedList<IENVSFrame>();
	}

	@Override
	public IENVSFrame pop() {
	    return (frameStack.size() == 0) ? null : frameStack.pop();
	}

	@Override
	public void push(IENVSFrame frame) {
		frameStack.push(frame);
	}
	
	@Override
	public void init(OID rootOID, ISBAStore store) {
		IComplexObject entryObject = (IComplexObject)store.retrieve(rootOID);
		IENVSFrame frame = createENVSFrame(entryObject, store);
		push(frame);
	}

	private IENVSFrame createENVSFrame(IComplexObject obj, ISBAStore store) {
		Collection<IENVSBinder> frameElements = new ArrayList<IENVSBinder>();
		
		for (OID childOID : obj.getChildOIDs()) {
			IENVSBinder binder = createENVSBinder(childOID, store);
			frameElements.add(binder);
		}
		
		return new ENVSFrame(frameElements);
	}

	private IENVSBinder createENVSBinder(OID childOID, ISBAStore store) {
		ISBAObject childObj = store.retrieve(childOID);
		IENVSBinder binder = new ENVSBinder(childObj);
		return binder;
	}

	private Collection<ISingleResult> findAll(String name, IENVSFrame frame) {
		Collection<ISingleResult> bagElements = new ArrayList<ISingleResult>();
		
		for (IENVSBinder binder : frame.getElements()) {
			if (binder.getName().equals(name)) {
				IAbstractQueryResult value = binder.getValue();
				
				if (value instanceof ISingleResult) {
					bagElements.add((ISingleResult)value);
				} else if (value instanceof IBagResult) {
					bagElements.addAll(((IBagResult)value).getElements());
				} else {
					throw new UnsupportedOperationException("Unsupported binder value.");
				}
			}
		}
		return bagElements;
	}

	@Override
    public IBagResult bind(String name) {
        Collection<ISingleResult> bagElements = new ArrayList<ISingleResult>();
        
        for (IENVSFrame frame : frameStack) {
            bagElements.addAll(findAll(name, frame));
            if (!bagElements.isEmpty()) break;
        }

        return new BagResult(bagElements);
    }
	
	@Override
	public IENVSFrame nested(IAbstractQueryResult result, ISBAStore store) {
		IENVSFrame frame = new ENVSFrame();
		
		if(result instanceof IReferenceResult) {
			IReferenceResult refRes = (IReferenceResult)result;
			ISBAObject sbaObj = store.retrieve(refRes.getOIDValue());
			
			if (sbaObj instanceof IComplexObject) {
				IComplexObject complexObj = (IComplexObject)sbaObj;
				frame = createENVSFrame(complexObj, store);
			} else if (sbaObj instanceof ISimpleObject) {
				ENVSBinder binder = new ENVSBinder(sbaObj);
				frame.getElements().add(binder);
			}
		} else if(result instanceof IBinderResult) {
			IBinderResult binderRes = (IBinderResult)result;
			ENVSBinder binder = new ENVSBinder(binderRes.getName(), binderRes.getValue());
			frame.getElements().add(binder);
		} else if(result instanceof IStructResult) {
			IStructResult structRes = (IStructResult)result;
			
			for (ISingleResult singleRes : structRes.elements()) {
				frame.getElements().addAll(nested(singleRes, store).getElements());
			}
		}
		return frame;
	}
}